/*
 *  Copyright (C) 2011 Jaime Pavlich-Mariscal
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package cl.ucn.disc.biblio.refcluster;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;

import cl.ucn.disc.biblio.refcluster.MultiCounter.ItemCounter;
import org.apache.log4j.Logger;

public class ReferenceManager {

	private static final String TMP_EXTENSION = ".tmp";
	public static final Logger LOGGER = Logger.getLogger(ReferenceClusterer.class.getName());
	private FileFilter wosFilesFilter = new FileFilter() {

		@Override
		public boolean accept(File pathname) {
			String filename = pathname.getName().toLowerCase();
			return filename.endsWith(".txt");
		}
	};
	private TextReplacementManager ac = new TextReplacementManager();

	/**
	 * Creates a list of references from a file in WoS format
	 * 
	 * @param wosFile
	 * @return
	 * @throws FileNotFoundException
	 */
	public Set<Reference> readRefsFromWoSFile(File wosFile) throws FileNotFoundException {
		Scanner s = new Scanner(wosFile);

		Set<Reference> refs = new TreeSet<Reference>();
		boolean addingRefs = false;

		while (s.hasNextLine()) {
			String line = s.nextLine();
			if (line.trim().endsWith("DOI")) { // Concatenates the DOI if it is
				// in the next line
				line = line.concat(s.nextLine());
			}

			try {
				if (line.startsWith("CR ")) {
					addingRefs = true;
					refs.add(new Reference(line.substring(3)));
				} else if (addingRefs && line.startsWith(" ")) {
					refs.add(new Reference(line.substring(3)));
				} else {
					addingRefs = false;
				}
			} catch (InvalidReferenceException e) {
				// do nothing
			}

		}

		return refs;

	}

	public List<Reference> readRefsFromListFile(File refsFile) throws FileNotFoundException {
		Scanner s = new Scanner(refsFile);
		List<Reference> refs = new ArrayList<Reference>();
		while (s.hasNextLine()) {
			String line = s.nextLine();

			try {
				refs.add(new Reference(line));
			} catch (RuntimeException e) {
				LOGGER.info(e.toString());
			} catch (InvalidReferenceException e) {
				// do nothing
			}
		}
		return refs;

	}

	public Set<Reference> readRefsFromWoSFolder(File wosDBFolder) throws FileNotFoundException {

		// Get all references from DB folder. Files in that folder must have WoS
		// format
		// and must be of the form "*.txt"
		Set<Reference> refs = new TreeSet<Reference>();
		for (File f : wosDBFolder.listFiles(wosFilesFilter)) {
			refs.addAll(readRefsFromWoSFile(f));
			LOGGER.info("Adding file: " + f.getName());
		}
		return refs;
	}

	public void saveRefsToList(Collection<Reference> refs, PrintWriter w) throws FileNotFoundException {
		for (Reference r : refs) {
			w.println(r.originalString);
		}
		w.flush();
	}

	/**
	 * Parses a set of references in WoS file of a source folder and writes a new one in the
	 * specified destination folder with the references replaced by a set of replacements obtained
	 * from a cluster file
	 * 
	 * @see #transformRefs(File, File, Map)
	 * @param srcFolder
	 * @param destFolder
	 * @param clusterFile
	 * @param authorClusterFile
	 * @throws FileNotFoundException
	 */
	public void replaceRefsOfFolder(File srcFolder, File destFolder, File clusterFile, File authorClusterFile) throws FileNotFoundException {
		deleteFilesInFolder(destFolder);
		ReferenceClusterer rc = new ReferenceClusterer();
		
		// Replaces references
		Collection<Set<Reference>> clusters = rc.readClusters(clusterFile);
		final Map<Reference, String> replacements = rc.createReplacementsFromClusters(clusters);
		TextTransformer referenceTransformer = new TextTransformer() {

			@Override
			public String transform(String text) throws TextTransformException {
				try {
					Reference r = new Reference(text);
					String rep = replacements.get(r);
					if (rep == null) {
						return r.ref;
					} else {
						return rep;
					}
				} catch (InvalidReferenceException e) {
					throw new TextTransformException(e);
				}
			}
		};
		
		replaceRefsOfFolder(srcFolder, destFolder, referenceTransformer);

		// Replaces authors
		if (authorClusterFile != null) {
			Collection<Set<String>> authorClusters = ac.readClusters(authorClusterFile);
			final Map<String,String> authorReplacements = ac.getReplacementsFromClusters(authorClusters);
			
			TextTransformer authorTransformer = new TextTransformer() {
				
				@Override
				public String transform(String text) throws TextTransformException {
					try {
						Reference r = new Reference(text);
						String authorReplacement = authorReplacements.get(r.getAuthor());
						if (authorReplacement != null) {
							r.setAuthor(authorReplacement);
							return r.ref;
						} else {
							return text;
						}

					} catch (InvalidReferenceException e) {
						throw new TextTransformException(e);
					}
				}
			};
			replaceRefsOfFolder(destFolder, destFolder, authorTransformer);
		}
	}

	private void deleteFilesInFolder(File folder) {
		for (File f : folder.listFiles()) {
			f.delete();
		}
	}

	/**
	 * Parses a set of references in a WoS file and writes a new one in the specified folder with
	 * the references replaced by a set of replacements
	 * 
	 * @param srcFile
	 * @param destFolder
	 * @param replacements
	 * @throws FileNotFoundException
	 */
	private void transformRefs(File srcFile, File destFolder, TextTransformer transformer) throws FileNotFoundException {
		Scanner s = new Scanner(srcFile);
		destFolder.mkdirs();
		File tmpFile = new File(destFolder, srcFile.getName() + TMP_EXTENSION);
		if (tmpFile.exists()) {
			tmpFile.delete();
		}
		PrintWriter w = new PrintWriter(tmpFile);

		boolean readingRefs = false;

		while (s.hasNextLine()) {

			// Reads the line from the source file
			String line = s.nextLine();

			// Concatenates the DOI if it is in the next line
			if (line.trim().endsWith("DOI")) {
				line = line + " " + s.nextLine().substring(3);
			}

			// Verifies if the line corresponds to a bibliographic reference
			// This happens either if the line starts with "CR" or if it is
			// among the following lines of a line that starts with CR
			try {
				if (line.startsWith("CR ")) {
					readingRefs = true;
					// line = "CR " + replace(line.substring(3), replacements);
					line = "CR " + transformer.transform(line.substring(3));// replace(new
																			// Reference(line.substring(3)),
																			// replacements);
				} else if (readingRefs && line.startsWith(" ")) {
					line = line.substring(0, 3) + transformer.transform(line.substring(3)); // +
																							// replace(new
																							// Reference(line.substring(3)),
																							// replacements);
				} else { // In this case the line is not a biblio reference
					readingRefs = false;
				}
			} catch (TextTransformException e) {
				// LOGGER.error(line + "\n\t" + e);
				// do nothing
			}

			// write the destination file
			w.println(line);
		}
		s.close();
		w.close();
		// Renames the temporary file to the destination file
//		dstFile = new File(destFolder, srcFile.getName()); 
		File dstFile = new File(destFolder, srcFile.getName());
		if (dstFile.exists()) {
			dstFile.delete();
		}
		tmpFile.renameTo(dstFile);
		
		tmpFile = new File(destFolder, srcFile.getName() + TMP_EXTENSION); // For some reason the tmp file is still there after renaming
		if (tmpFile.exists()) {
			tmpFile.delete();
		}
	}

	/**
	 * Replaces the references of all WoS files in a folder.
	 * 
	 * @see ReferenceManager#transformRefs(File, File, Map)
	 * @param srcFolder
	 * @param destFolder
	 * @param replacements
	 * @throws FileNotFoundException
	 */
	private void replaceRefsOfFolder(File srcFolder, File destFolder, TextTransformer textTransformer) throws FileNotFoundException {
		LOGGER.info("Writing references to " + destFolder);


		for (File f : srcFolder.listFiles(wosFilesFilter)) {
			transformRefs(f, destFolder,textTransformer);
		}
		LOGGER.info("DONE");

	}

	/**
	 * Creates a "super reference", i.e., a reference having the longest sentences for each field
	 * found in a list of references
	 * 
	 * @param refs
	 * @return
	 */
	public Reference createSuperReference(Set<Reference> refs) {

		Comparator<MultiCounter.ItemCounter<String>> c = new Comparator<MultiCounter.ItemCounter<String>>() {

			@Override
			public int compare(MultiCounter.ItemCounter<String> o1, MultiCounter.ItemCounter<String> o2) {
				int cmp = -o1.count.compareTo(o2.count);

				if (cmp == 0) {
					cmp = -o1.element.compareTo(o2.element);
				}
				return cmp;
			}
		};

		MultiCounter<String> authorCount = new MultiCounter<String>(c, false);
		MultiCounter<String> yearCount = new MultiCounter<String>(c, false);
		MultiCounter<String> journalCount = new MultiCounter<String>(c, false);
		MultiCounter<String> volCount = new MultiCounter<String>(c, false);
		MultiCounter<String> pageCount = new MultiCounter<String>(c, false);
		MultiCounter<String> doiCount = new MultiCounter<String>(c, false);

		for (Reference ref : refs) {
			String author = StringUtils.trimToNull(ref.getAuthor());
			String year = StringUtils.trimToNull(ref.getYear());
			String journal = StringUtils.trimToNull(ref.getJournal());
			String volume = StringUtils.trimToNull(ref.getVolume());
			String page = StringUtils.trimToNull(ref.getPage());
			String doi = StringUtils.trimToNull(ref.getDoi());

			authorCount.add(author);
			yearCount.add(year);
			journalCount.add(journal);
			volCount.add(volume);
			pageCount.add(page);
			doiCount.add(doi);

		}

		String author = authorCount.isEmpty() ? null : StringUtils.trimToNull(authorCount.orderedCountersIterator().next().element);
		String year = yearCount.isEmpty() ? null : StringUtils.trimToNull(yearCount.orderedCountersIterator().next().element);
		String journal = journalCount.isEmpty() ? null : StringUtils.trimToNull(journalCount.orderedCountersIterator().next().element);
		String volume = volCount.isEmpty() ? null : StringUtils.trimToNull(volCount.orderedCountersIterator().next().element);
		String page = pageCount.isEmpty() ? null : StringUtils.trimToNull(pageCount.orderedCountersIterator().next().element);
		String doi = doiCount.isEmpty() ? null : StringUtils.trimToNull(doiCount.orderedCountersIterator().next().element);

		return new Reference(author, year, journal, volume, page, doi);

	}

}
